-----------The Flying Carpet-----------
A 4am crack                  2015-12-30
---------------------------------------

Name: The Flying Carpet
Genre: educational
Year: 1985
Publisher: Litag
Media: single-sided 5.25-inch floppy
OS: DOS 3.3
Other cracks: none
Similar cracks:
  #511 Monkey Business
  #369 Alpine Tram Ride
  #290 Sliding Block

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  no errors, but copy loads DOS, runs a
  startup program, then reboots

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Disk Fixer
  T00 -> looks like DOS 3.3 RWTS
  T00-T02 -> looks like full DOS 3.3
  T01,S09 -> startup program is HELLO3

Why didn't any of my copies work?
  probably a nibble check in the
  startup program

A well-timed <Ctrl-Reset> gets me a
prompt, but any command reboots again.

Next steps:

  1. Load up the startup program from
     my work disk
  2. Find and disable the nibble check
  3. There is no step 3 (I hope)

                   ~

               Chapter 1
   Hide Your Disk Catalog With This
  One Weird Trick! Crackers Hate It!


Booting from my work disk, my non-
working copy ought to have a catalog,
but it has suspiciously vanished.

[S6,D1=non-working copy from COPYA]
[S5,D1=my work disk]

]PR#5
...

]CATALOG,S6,D1

C1983 DSR^C#254
294 FREE

]

Wait, what?

[Disk Fixer]
  --> T11,S00

Looks like a standard DOS 3.3 VTOC. It
points to T11,S0F as the first catalog
sector.

  --> T11,S0F

Blank. In fact, the entire rest of the
track is blank.

  --> T10,S0F nope
  --> T0F,S0F nope
  --> T0E,S0F nope
      ...
  --> T03,S0F

Ah, there it is!

                 --v--

-------------- DISK EDIT --------------
TRACK $03/SECTOR $0F/VOLUME $FE/BYTE$00
---------------------------------------
$00:>00<03 0E 00 00 00 00 00   @CN@@@@@
$08: 00 00 00 12 0F 02 C8 C5   @@@ROBHE
$10: CC CC CF A0 A0 A0 A0 A0   LLO
$18: A0 A0 A0 A0 A0 A0 A0 A0
$20: A0 A0 A0 A0 A0 A0 A0 A0
$28: A0 A0 A0 A0 03 00 12 0C       C@RL
$30: 02 D0 C7 CD A0 A0 A0 A0   BPGM
$38: A0 A0 A0 A0 A0 A0 A0 A0
$40: A0 A0 A0 A0 A0 A0 A0 A0
$48: A0 A0 A0 A0 A0 A0 A0 12          R
$50: 00 13 0A 84 C2 D5 D3 A0   @SJ.BUS
$58: A0 A0 A0 A0 A0 A0 A0 A0
$60: A0 A0 A0 A0 A0 A0 A0 A0
$68: A0 A0 A0 A0 A0 A0 A0 A0
$70: A0 A0 05 00 13 05 04 D5     E@SEDU
$78: CE D0 C1 C3 CB C5 D2 A0   NPACKER
---------------------------------------
BUFFER 0/SLOT 6/DRIVE 1/MASK OFF/NORMAL

---------------------------------------
COMMAND : _

                 --^--

But how does the original disk know
where to look? I scoured "Beneath Apple
DOS" until I found the answer on p8-28:

                 --v--

B011-B036 Read a directory sector
; (If CARRY flag is zero on entry, read
  first directory sector. If CARRY is
  one, read next)
; Memorize entry code.
; Set buffer pointers (B045).
; First or next?
; If first, get track/sector of
  directory sector from VTOC at offset
  +1,+2.
; Otherwise, get track/sector from
  directory sector at offset +1,+2. If
  track is zero, exit with error code
  (end of directory).
; Call RWTS to read sector.
; Exit with normal return code.

                 --^--

So, to read the first sector of file
names and other metadata, this routine
is supposed to look at the VTOC sector
buffer (read from T11,S00 and stored at
$B3BB..$B4BA). The VTOC says "hey, the
first sector of files and stuff is in
T11,S0F" so this routine is supposed to
read T11,S0F.

But the DOS on this disk made one small
modification to that routine. (This is
on T01,S0F.)

B011-   08          PHP
B012-   20 45 B0    JSR   $B045
B015-   28          PLP
B016-   B0 08       BCS   $B020
B018-   AC BD B3    LDY   $B3BD
                                ------
B01B-   A2 03       LDX   #$03  << hey
B01D-   EA          NOP         << now
                                ------
B01E-   D0 0A       BNE   $B02A
B020-   AE BC B4    LDX   $B4BC
B023-   D0 02       BNE   $B027
B025-   38          SEC
B026-   60          RTS
B027-   AC BD B4    LDY   $B4BD
B02A-   8E 97 B3    STX   $B397
B02D-   8C 98 B3    STY   $B398
B030-   A9 01       LDA   #$01
B032-   20 52 B0    JSR   $B052
B035-   18          CLC
B036-   60          RTS

Instead of getting the track number
from the VTOC, it hard-codes track $03.

Now that I've identified the problem,
the fix is straightforward. If I change
the VTOC header (T11,S00) to point to
the actual first directory sector
(T03,S0F), DOS 3.3 or any other copy
utility should be able to read the
disk catalog.

T11,S00,$01 change "11" to "03"

]PR#5
...
]CATALOG,S6,D1

C1983 DSR^C#254
250 FREE

 A 003 HELLO
 A 018 PGM
*B 005 BUS
 B 002 UNPACKER
 B 034 TITLE
*B 031 TITLE-1
*B 031 TITLE-2
*B 002 PR
*B 018 P1
*B 018 P2
*B 055 P3
*B 002 SOUND
 B 017 FLYING CARPET.PAC
*B 002 HELLO3

                   ~

               Chapter 2
         In Which We Dive Into
           Hostile Territory


]BLOAD HELLO3
]CALL -151

*BF55.BF56

BF55- 00 90

(Diversi-DOS 64K stores the program
start location in $BF55. In standard
DOS 3.3, it's at $AA72.)

*9000L

; get address of RWTS parameter table
9000-   20 E3 03    JSR   $03E3
9003-   85 FB       STA   $FB
9005-   84 FA       STY   $FA

; hmm
9007-   A9 C5       LDA   #$C5
9009-   48          PHA

; copy RWTS parameters
900A-   A9 00       LDA   #$00
900C-   85 FC       STA   $FC
900E-   A2 03       LDX   #$03
9010-   BC 49 90    LDY   $9049,X
9013-   91 FA       STA   ($FA),Y
9015-   CA          DEX
9016-   10 F8       BPL   $9010
9018-   8A          TXA

; hmm
9019-   48          PHA

The X register will be $FF after the
loop, so this means we've pushed $C5FF
to the stack. At this point, a bare RTS
will "return" to $C5FF+1, i.e. reboot.

901A-   20 3C 90    JSR   $903C

*903CL

; call the RWTS (most likely just to
; move the drive head to the proper
; position for an impending nibble
; check)
903C-   20 E3 03    JSR   $03E3
903F-   20 D9 03    JSR   $03D9
9042-   A9 00       LDA   #$00
9044-   85 48       STA   $48

; if that fails, off to The Badlands
9046-   B0 5E       BCS   $90A6
9048-   60          RTS

Caller was $901A, so resuming at $901D:

*901DL

901D-   A0 01       LDY   #$01
901F-   B1 FA       LDA   ($FA),Y
9021-   AA          TAX
9022-   20 59 90    JSR   $9059

*9059L

; turning on the drive motor manually
; is always suspicious
9059-   BD 89 C0    LDA   $C089,X

; initialize death counters
905C-   A9 56       LDA   #$56
905E-   85 FD       STA   $FD
9060-   A9 08       LDA   #$08
9062-   C6 FC       DEC   $FC
9064-   D0 04       BNE   $906A
9066-   C6 FD       DEC   $FD

; if death counter hits 0, give up
9068-   F0 3C       BEQ   $90A6

; look for an $FB nibble
906A-   BC 8C C0    LDY   $C08C,X
906D-   10 FB       BPL   $906A
906F-   C0 FB       CPY   #$FB
9071-   D0 ED       BNE   $9060
9073-   F0 00       BEQ   $9075

; kill a few cycles (not pointless,
; because the disk spins independently
; of the CPU, so all of these low-level
; disk reads are highly time-sensitive)
9075-   EA          NOP
9076-   EA          NOP

; read data latch (note: no BPL loop
; here, we're just reading it once)
9077-   BC 8C C0    LDY   $C08C,X

; do a compare to set or clear the
; carry bit (among other things, but
; it's the carry bit we care about)
907A-   C0 08       CPY   #$08

; rotate the carry into the low bit of
; the accumulator
907C-   2A          ROL

; if we just rolled a "1" bit out of
; the high bit of the accumulator, take
; this branch
907D-   B0 0B       BCS   $908A

; next nibble needs to be $FF
907F-   BC 8C C0    LDY   $C08C,X
9082-   10 FB       BPL   $907F

; ...otherwise we start over
9084-   C0 FF       CPY   #$FF
9086-   D0 D8       BNE   $9060

; loop back to get next nibble
9088-   F0 EB       BEQ   $9075

; execution continues here (from $907D)
; get another nibble
908A-   BC 8C C0    LDY   $C08C,X
908D-   10 FB       BPL   $908A

; stash it in zero page
908F-   84 FC       STY   $FC

; if the accumulator is anything but
; %00001010, start over
9091-   C9 0A       CMP   #$0A
9093-   D0 CB       BNE   $9060

I got lost several times trying to
follow this routine. I think the
easiest way to explain it is to show
the difference between the original
disk and my non-working copy.

Here is the original disk, as seen by
the Copy II+ nibble editor. Nibbles
with extra "0" bits (timing bits) after
them are displayed in inverse on an
original machine, marked here with a
"+" after the nibble.

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK:     START: 1B1E  LENGTH: 17C1

1C70: 9F EB E5 FC D7 D7 D7 EE   VIEW
1C78: FA E6 E6 FF FE F2 ED FD
1C80: FF EF ED BA BB DD AF E6
1C88: B7 A7 CB B7 DE AA EB FF
1C90: FF FF FF FB+FF FF+FF FF+
1C98: FD FF+FF+FF+FF+FF+FF+FF+
1CA0: FF+FF+D5 AA 96 AA AB AA
1CA8: AA AA AB AA AA DE AA EB+
1CB0: FF+FF+FF+FF+FF+FF D5 AA
---------------------------------------

  A  TO ANALYZE DATA  ESC TO QUIT

  ?  FOR HELP SCREEN  /  CHANGE PARMS

  Q  FOR NEXT TRACK   SPACE TO RE-READ

                 --^--

It's easy to understand why a simple
sector copy failed. The sequence that
this code is looking for starts at
offset $1C93, which is between the end
of one sector and the beginning of the
next. (The data epilogue is at $1C8C;
the next address prologue is at $1CA2.)
Sector copiers discard everything
between those delimiters and rebuild
the track with a default pattern of
sync bytes. That pattern doesn't
include an $FB nibble, so the nibble
check fails.

But the EDD bit copy also failed. Here
is the original disk's pattern at
offset $1C93:

  - $FB + timing bit
  - $FF
  - $FF + timing bit
  - $FF
  - $FF + timing bit

And here is what the same part of the
track looks like on my failed EDD copy:

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK:     START: 1B1E  LENGTH: 17C1

1C70: 9F EB E5 FC D7 D7 D7 EE   VIEW
1C78: FA E6 E6 FF FE F2 ED FD
1C80: FF EF ED BA BB DD AF E6
1C88: B7 A7 CB B7 DE AA EB FF
1C90: FF FF FF FB+FF FF FF+FF+
1C98: FD FF+FF+FF+FF+FF+FF+FF+
1CA0: FF+FF+D5 AA 96 AA AB AA
1CA8: AA AA AB AA AA DE AA EB+
1CB0: FF+FF+FF+FF+FF+FF D5 AA
---------------------------------------

  A  TO ANALYZE DATA  ESC TO QUIT

  ?  FOR HELP SCREEN  /  CHANGE PARMS

  Q  FOR NEXT TRACK   SPACE TO RE-READ

                 --^--

A subtle difference! The sequence at
offset $1C93 now looks like this:

  - $FB + timing bit
  - $FF
  - $FF
  - $FF + timing bit
  - $FF + timing bit

This code is looking for $FF bytes with
an alternating pattern of timing bit,
no timing bit, timing bit, no timing
bit. It doesn't find that on the bit
copy, so it knows it's not running on
an original disk.

                   ~

               Chapter 3
    In Which We Stare Into Infinity
       And Infinity Stares Back
      And Executes A DOS Command


Continuing from $9095...

*9095L

; get a nibble
9095-   BD 8C C0    LDA   $C08C,X
9098-   10 FB       BPL   $9095

; more bit twiddling
909A-   38          SEC
909B-   2A          ROL

; AND it with the previously stashed
; nibble
909C-   25 FC       AND   $FC
909E-   49 FF       EOR   #$FF

; branch to failure path
90A0-   D0 04       BNE   $90A6

; success path falls through to here --
; turn off the drive motor and return
; to the caller
90A2-   DD 88 C0    CMP   $C088,X
90A5-   60          RTS

; failure path (a.k.a. "The Badlands",
; from which there is no return)
90A6-   A8          TAY
90A7-   DD 88 C0    CMP   $C088,X

; manually pop the return address of
; the immediate caller (which leaves
; the manually pushed $C5FF address on
; the top of the stack to "return" to)
90AA-   68          PLA
90AB-   68          PLA

; destroy all trace of this program in
; memory
90AC-   99 00 90    STA   $9000,Y
90AF-   C8          INY
90B0-   C0 8B       CPY   #$8B
90B2-   D0 F8       BNE   $90AC

; "return" to $C5FF+1, i.e. reboot
90B4-   60          RTS

That explains the behavior that I saw
on my non-working copy (endlessly
rebooting).

This was called from $9022, so let's
continue from $9025.

*9025L

; pop the bogus return value ($C5FF)
; off the stack
9025-   68          PLA
9026-   68          PLA

; Route a series of bytes through the
; DOS output vector at ($36). This is
; how binary files "execute" DOS 3.3
; commands, like PRINT CHR$(4)"..." in
; Applesoft BASIC. This is totally
; legitimate code, cleverly disguised
; as an infinite loop.
9027-   A0 00       LDY   #$00
9029-   84 FE       STY   $FE
902B-   B9 4D 90    LDA   $904D,Y
902E-   09 80       ORA   #$80
9030-   20 39 90    JSR   $9039
9033-   A4 FE       LDY   $FE
9035-   C8          INY
9036-   4C 29 90    JMP   $9029
9039-   6C 36 00    JMP   ($0036)

Let's see what it's printing.

*FC58G N 400<904D.9058M

DRUN HELLOM@
^         ^^ control characters

Aha! It's not an infinite loop after
all. Well, it is, technically, but it
won't actually run forever. It's not
easy to show in plain text, but the
initial "D" and the final "M@" are in
inverse. So that string starts with
Ctrl-D and ends with Ctrl-M and a null
byte. Since DOS is already loaded,
printing this through the DOS vector
will execute that command, as if you
typed it from a prompt yourself. Since
that program never returns to the
caller, it will break out of the
seemingly "infinite" loop.

Since there are no side effects to the
copy protection routine, I can change
this file to simply skip the call to
$9059 and continue on to running the
real startup program. Using my trusty
Disk Fixer sector editor, I press "D"
for directory mode, select "HELLO3",
and I find this code on T20,S05.

T20,S02,$26 change 20 to 2C

Quod erat liberandum.

---------------------------------------
A 4am crack                     No. 546
------------------EOF------------------
